package com.lzj.server.aspect;

import cn.hutool.core.util.ArrayUtil;
import com.lzj.server.aspect.annotation.VideoCache;
import com.lzj.server.constant.RedisConstant;
import com.lzj.server.utils.RedisUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.Collectors;

/**
 * @author Lzj
 * @date 2025/5/10
 * @description 自定义切面类【自动加缓存】
 */
@Slf4j
@Component
@Aspect
public class VideoAspect {
    @Resource
    private RedissonClient redissonClient;

    @Around("@annotation(videoCache)")
    public Object doConcurrentOperation(ProceedingJoinPoint joinPoint, VideoCache videoCache) throws Throwable {
        try {
            // 获取切点方法返回值类型
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            // 拿到返回值类型字节码
            Class<?> returnType = method.getReturnType();
            // 初始返回值
            Object obj = returnType.getDeclaredConstructor().newInstance();
            // 构建redis key
            String prefix = videoCache.prefix();
            // 获取切点方法参数
            Object[] args = joinPoint.getArgs();
            String suffix = "none";
            if (ArrayUtil.isNotEmpty(args)) {
                suffix = Arrays.stream(args).map(String::valueOf).collect(Collectors.joining(":"));
            }
            String key = prefix + suffix;
            // 查缓存
            boolean hasKey = RedisUtils.hasKey(key);
            if (hasKey) {
                // 命中缓存
                return RedisUtils.getObjectBean(key, returnType);
            } else {
                // 未命中缓存 先获取锁
                //构造redisson key
                String lockKey = RedisConstant.VIDEO_LOCK_PREFIX + prefix + suffix;
                RLock lock = redissonClient.getLock(lockKey);
                lock.lock();
                try {
                    // 后面没获取到锁的线程会阻塞在这里,拿到锁之后防止再查数据库，这里再查一次缓存
                    hasKey = RedisUtils.hasKey(key);
                    if (hasKey) {
                        obj = RedisUtils.getObjectBean(key, returnType);
                        // 命中缓存
                        return obj;
                    }
                    // 查数据库
                    obj = joinPoint.proceed();
                    // 获取超时时间 空对象和存在数据 定义不同的超时时间
                    long ttl = obj == null ? RedisConstant.VIDEO_TEMPORARY_TIMEOUT: RedisConstant.VIDEO_TIMEOUT;
                    //刷新缓存 如果为NULL也缓 防止缓存穿透
                    RedisUtils.set(key, obj, ttl);
                    return obj;
                } finally {
                    if (lock.isHeldByCurrentThread()) {
                        lock.unlock();
                    }
                }
            }
        } catch (Throwable e) {
            log.error("缓存处理异常", e);
            return joinPoint.proceed();
        }
    }
}
